From 04fabf115ccea31ac504b06cc51d78423b697f60 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Tue, 28 Jul 2026 15:07:57 +0200 Subject: refactor: services layer, valibot validation, shared CSS utilities, aria labels, and drizzle migrate - Extract groceries/categories/references services with db injection - Add valibot for form validation and safe numeric id parsing - Fix category delete FK behavior with onDelete: set null + migration - Replace custom migrate.js with drizzle-orm/libsql/migrator - Add shared CSS utility classes in app.css (forms, lists, buttons) - Add aria-pressed and aria-label on interactive elements - Remove redundant app.css imports and unused /stores import - Add integration tests for all services using drizzle migrate helper - Remove smoke test; add db:migrate and db:studio scripts --- src/routes/grocery/[id]/+page.server.ts | 58 +++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 24 deletions(-) (limited to 'src/routes/grocery/[id]/+page.server.ts') diff --git a/src/routes/grocery/[id]/+page.server.ts b/src/routes/grocery/[id]/+page.server.ts index b19825a..3c3967a 100644 --- a/src/routes/grocery/[id]/+page.server.ts +++ b/src/routes/grocery/[id]/+page.server.ts @@ -1,11 +1,28 @@ import { error, fail, redirect } from '@sveltejs/kit'; import { db } from '$lib/server/db'; -import { groceries, categories, references } from '$lib/server/db/schema'; +import { groceries, categories } from '$lib/server/db/schema'; import { eq } from 'drizzle-orm'; +import { + loadForGrocery, + add as addRef, + deleteReference, +} from '$lib/server/services/references'; +import { deleteGrocery } from '$lib/server/services/groceries'; +import { + numericId, + parseFormString, + parseOptionalNumber, +} from '$lib/server/services/validation'; +import type { Grocery, Reference } from '$lib/server/services/types'; import type { Actions, PageServerLoad } from './$types'; +export interface GroceryDetailOutput { + item: Grocery & { categoryName: string | null }; + references: Reference[]; +} + export const load: PageServerLoad = async ({ params }) => { - const id = Number(params.id); + const id = numericId(params.id); const [item] = await db.select().from(groceries).where(eq(groceries.id, id)); if (!item) throw error(404, 'Article introuvable'); @@ -17,10 +34,7 @@ export const load: PageServerLoad = async ({ params }) => { .where(eq(categories.id, item.categoryId)) : [null]; - const refs = await db - .select() - .from(references) - .where(eq(references.groceryId, id)); + const refs = await loadForGrocery(id); return { item: { @@ -34,38 +48,34 @@ export const load: PageServerLoad = async ({ params }) => { export const actions: Actions = { add: async ({ request, params }) => { const data = await request.formData(); - const reference = data.get('reference')?.toString().trim(); - const price = data.get('price')?.toString().trim(); - const unit = data.get('unit')?.toString().trim(); - const qualityScore = data.get('quality_score')?.toString().trim(); + const groceryId = numericId(params.id); + const reference = parseFormString(data.get('reference')); + const price = parseOptionalNumber(data.get('price')); + const unit = parseFormString(data.get('unit')); + const qualityScore = parseOptionalNumber(data.get('quality_score')); if (!reference) { return fail(400, { error: 'La référence est requise' }); } - await db.insert(references).values({ - groceryId: Number(params.id), - reference, - price: price ? Number(price) : null, - unit: unit || null, - qualityScore: qualityScore ? Number(qualityScore) : null, - }); - + await addRef(groceryId, reference, price, unit, qualityScore); return { success: true }; }, delete: async ({ request }) => { const data = await request.formData(); - const id = Number(data.get('id')); - - await db.delete(references).where(eq(references.id, id)); - + try { + const id = numericId(data.get('id')); + await deleteReference(id); + } catch { + return fail(400, { error: 'ID invalide' }); + } return { success: true }; }, deleteItem: async ({ params }) => { - await db.delete(groceries).where(eq(groceries.id, Number(params.id))); - + const groceryId = numericId(params.id); + await deleteGrocery(groceryId); throw redirect(303, '/'); }, }; -- cgit v1.3